package com.xiamaliu.spring.context;

import com.xiamaliu.spring.annotation.GPAutowired;
import com.xiamaliu.spring.annotation.GPController;
import com.xiamaliu.spring.annotation.GPService;
import com.xiamaliu.spring.aop.support.GPAdvisedSupport;
import com.xiamaliu.spring.aop.config.GPAopConfig;
import com.xiamaliu.spring.aop.GPJdkDynamicAopProxy;
import com.xiamaliu.spring.beans.GPBeanWrapper;
import com.xiamaliu.spring.beans.config.GPBeanDefinition;
import com.xiamaliu.spring.beans.support.GPBeanDefinitionReader;
import lombok.NoArgsConstructor;

import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

@NoArgsConstructor
public class GPApplicationContext {

    // 存储注册信息的 BeanDefinition
    protected final Map<String, GPBeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, GPBeanDefinition>();

    private String[] configLocations;

    private GPBeanDefinitionReader reader;

    // 单例的 IOC 容器缓存
    private Map<String, Object> factoryBeanObjectCache = new ConcurrentHashMap<String, Object>();

    // 通用 IOC 容器
    private Map<String, GPBeanWrapper> factoryBeanInstanceCache = new ConcurrentHashMap<String, GPBeanWrapper>();

    public GPApplicationContext(String... configLocations) {
        this.configLocations = configLocations;
        try {
            // 1、定位，定位配置文件
            reader = new GPBeanDefinitionReader(this.configLocations);

            // 2、加载配置文件，扫描相关的类，把他们封装成 BeanDefinition
            List<GPBeanDefinition> beanDefinitions = reader.loadBeanDefinitions();

            // 3、注册，把配置信息放到容器里面（伪 IOC 容器）
            doRegisterBeanDefinition(beanDefinitions);

            // 4、完成自动依赖注入
            doAutoWrited();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 只处理非延时加载的情况
    private void doAutoWrited() {
        for (Map.Entry<String, GPBeanDefinition> beanDefinitionEntry : this.beanDefinitionMap.entrySet()) {
            String beanName = beanDefinitionEntry.getKey();
            try {
                getBean(beanName);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private void doRegisterBeanDefinition(List<GPBeanDefinition> beanDefinitions) throws Exception {
        for (GPBeanDefinition beanDefinition : beanDefinitions) {
            if (this.beanDefinitionMap.containsKey(beanDefinition.getFactoryBeanName())) {
                throw new Exception("The " + beanDefinition.getFactoryBeanName() + " is exists !!!");
            }
            this.beanDefinitionMap.put(beanDefinition.getBeanClassName(), beanDefinition);
            this.beanDefinitionMap.put(beanDefinition.getFactoryBeanName(), beanDefinition);
        }
        // 到这里未知，容器初始化完毕
    }

    public Object getBean(Class<?> beanClass) {
        return getBean(beanClass.getName());
    }

    // 依赖注入，从这里开始，通过读取BeanDefinition中的信息
    // 然后，通过反射机制创建一个实例并返回
    // Spring的做法是：不会吧最原始的对象放进去，会用一个BeanWrapper来进行一次包装
    // 装饰器模式：
    // 1、保留原来的OOP关系
    // 2、我需要对他进行扩展，增强（为了以后得AOP打基础）
    public Object getBean(String beanName) {
        // 1、读取配置信息
        GPBeanDefinition gpBeanDefinition = this.beanDefinitionMap.get(beanName);

        // 2、实例化
        Object instance = instantiateBean(beanName, gpBeanDefinition);

        // 3、把这个对象封装到BeanWrapper中
        GPBeanWrapper beanWrapper = new GPBeanWrapper(instance);

        // 4、把BeanWrapper存到IOC容器里面

        // 5、拿到BeanWrapper之后，把BeanWrapper保存到IOC容器中
        this.factoryBeanInstanceCache.put(beanName, beanWrapper);

        // 6、执行依赖注入
        populateBean(beanName, new GPBeanDefinition(), beanWrapper);

        return this.factoryBeanInstanceCache.get(beanName).getWrappedInstance();
    }

    private void populateBean(String beanName, GPBeanDefinition gpBeanDefinition, GPBeanWrapper gpBeanWrapper) {
        Object instance = gpBeanWrapper.getWrappedInstance();

        Class<?> clazz = gpBeanWrapper.getWrappedClass();

        // 判断只有加了注解的类，才执行依赖注入
        if (!(clazz.isAnnotationPresent(GPController.class) || clazz.isAnnotationPresent(GPService.class))) {
            return;
        }

        // 获得所有的fields
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            if (!field.isAnnotationPresent(GPAutowired.class)) {
                continue;
            }

            GPAutowired autowired = field.getAnnotation(GPAutowired.class);

            String autowiredBeanName = autowired.value().trim();
            if ("".equals(autowiredBeanName)) {
                autowiredBeanName = field.getType().getName();
            }

            // 强制访问
            field.setAccessible(true);

            try {
                if (this.factoryBeanInstanceCache.get(autowiredBeanName) == null) {
                    continue;
                }
                field.set(instance, this.factoryBeanInstanceCache.get(autowiredBeanName).getWrappedInstance());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /*private Object instantiateBean(String beanName, GPBeanDefinition gpBeanDefinition) {
        // 1、拿到要实例化的对象的类名
        String className = gpBeanDefinition.getBeanClassName();

        // 2、反射实例化，得到一个对象
        Object instance = null;
        try {
            if (this.factoryBeanObjectCache.containsKey(className)) {
                instance = this.factoryBeanObjectCache.get(className);
            } else {
                Class<?> clazz = Class.forName(className);
                instance = clazz.newInstance();
                this.factoryBeanObjectCache.put(className, instance);
                this.factoryBeanObjectCache.put(gpBeanDefinition.getFactoryBeanName(), instance);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return instance;
    }*/

    private Object instantiateBean(String beanName, GPBeanDefinition gpBeanDefinition) {
        // 1、拿到要实例化的对象的类名
        String className = gpBeanDefinition.getBeanClassName();

        Object instance = null;
        try {
            Class<?> clazz = Class.forName(className);
            instance = clazz.newInstance();

            GPAdvisedSupport config = instantionAopConfig(gpBeanDefinition);
            config.setTargetClass(clazz);
            config.setTarget(instance);

            // 符合 PointCut 规则的话，创建代理对象
            if (config.pointCutMatch()) {
                instance = new GPJdkDynamicAopProxy(config).getProxy();
            }

            this.factoryBeanObjectCache.put(beanName, instance);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return instance;
    }

    private GPAdvisedSupport instantionAopConfig(GPBeanDefinition beanDefinition) {
        GPAopConfig config = new GPAopConfig();
        config.setPointCut(this.reader.getConfig().getProperty("pointCut"));
        config.setAspectClass(this.reader.getConfig().getProperty("aspectClass"));
        config.setAspectBefore(this.reader.getConfig().getProperty("aspectBefore"));
        config.setAspectAfter(this.reader.getConfig().getProperty("aspectAfter"));
        config.setAspectAfterThrow(this.reader.getConfig().getProperty("aspectAfterThrow"));
        config.setAspectAfterThrowingName(this.reader.getConfig().getProperty("aspectAfterThrowingName"));
        return new GPAdvisedSupport(config);
    }

    public String[] getBeanDefinitionNames() {
        return this.beanDefinitionMap.keySet().toArray(new String[this.beanDefinitionMap.size()]);
    }

    public int getBeanDefinitionCount() {
        return this.beanDefinitionMap.size();
    }

    public Properties getConfig() {
        return this.reader.getConfig();
    }
}
